package ar.uba.is2.ec.matem.exec.impl;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

import org.apache.log4j.Logger;

import ar.uba.is2.common.Const;
import ar.uba.is2.common.mensaje.Mensaje;
import ar.uba.is2.common.mensaje.TipoMensaje;
import ar.uba.is2.common.mensaje.contenido.ContenidoDatoSuscripto;
import ar.uba.is2.common.mensaje.contenido.Dato;
import ar.uba.is2.common.mensaje.contenido.Identificador;
import ar.uba.is2.common.mensaje.contenido.TipoDato;
import ar.uba.is2.common.threads.ThreadManager;
import ar.uba.is2.ec.config.Contexto;
import ar.uba.is2.ec.matem.algoritmo.Algoritmo;
import ar.uba.is2.ec.matem.algoritmo.AlgoritmoPrimeraRegla;
import ar.uba.is2.ec.matem.algoritmo.MissingDataException;
import ar.uba.is2.ec.matem.concurrent.DatoNoEncontradoCallback;
import ar.uba.is2.ec.matem.dao.ResultadoDeModeloDao;
import ar.uba.is2.ec.matem.dao.impl.ResultadoDeModeloDaoImpl;
import ar.uba.is2.ec.matem.datos.ResultadoDeModelo;
import ar.uba.is2.ec.matem.exec.AdministradorMemoriaCompartida;
import ar.uba.is2.ec.matem.exec.Ejecutor;
import ar.uba.is2.ec.matem.modelo.ModeloEjecutable;
import ar.uba.is2.ec.matem.modelo.impl.DatosFetcher;
import ar.uba.is2.ec.matem.motor.Regla;
import ar.uba.is2.ec.scmanager.ReceptorSuscripcionDeDatos;

public class EjecutorConcurrente implements Ejecutor, DatoNoEncontradoCallback {

	private static int nroMensaje = 1;
	
	private static final int SLEEP_TIME = 5000;
	private static final Logger logger = Logger.getLogger(EjecutorConcurrente.class);
	
	private boolean ocupado = false;
	private long waitForMissingData;

	public EjecutorConcurrente() {
		logger.debug("Instanciando ejecutor");
		this.waitForMissingData = Long.parseLong(Contexto.instance.get("ec.modelo.matematico.exec.missing.data.time"));
	}
	
	public void init() {
		ThreadManager.instance.crearYCorrerThread(this);
	}
	
	/**
	 * Cola de modelos a ejecutar. Para esta iteracion se utiliza una
	 * {@link SynchronousQueue} que permite encolar 1 solo modelo a la vez
	 */
	private BlockingQueue<ModeloEjecutable> colaDeModelos = new SynchronousQueue<ModeloEjecutable>();
	private ResultadoDeModeloDao resultadoDao = new ResultadoDeModeloDaoImpl();
	
	@Override
	public void run() {
		while (true) {
			try {
				ModeloEjecutable modelo = colaDeModelos.take();
				
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Ejecutando modelo %s", modelo));
				}
				ocupado = true;

				Algoritmo algoritmo = modelo.getAlgoritmo();
				Collection<Regla> reglas = modelo.getReglas();
				Collection<Dato> datos = modelo.getDatos();
				
				((AlgoritmoPrimeraRegla) algoritmo).registrarCallback(this);
				ResultadoDeModelo resultado = algoritmo.ejecutar(modelo.getIdModelo(), reglas, datos);
				
				this.resultadoDao.guardar(resultado);
				this.reenviarSuscripcionExterna(resultado);
				
				logger.info(String.format("Modelo terminado resultado: %s ", resultado));

				AdministradorMemoriaCompartida.getInstance().agregarDato(resultado.getIdentificador(), resultado);
				
				ocupado = false;
			} catch (InterruptedException e) {
				break;
			} catch (MissingDataException e) {
				logger.error("Error en la ejecucion de modelo", e);
			}
		}
	}
	
	private void reenviarSuscripcionExterna(ResultadoDeModelo resultado) {
		Identificador identificador = new Identificador(TipoDato.RESULTADO, Contexto.instance.getConfiguracionGeneralEC().getCoordenada(), resultado.getIdModelo());
		
		if (ReceptorSuscripcionDeDatos.identificadoresSuscriptos.contains(identificador)) {
			Mensaje msj = new Mensaje();
			
			msj.setOrigen(Contexto.instance.getConfiguracionGeneralEC().getComponenteEc());
			msj.setDestino(Const.COMPONENTE_SERVIDOR_CENTRAL);
			msj.setNumeroMensaje(nroMensaje++);
			msj.setFechaCreacion(new Date());
			msj.setTipoMensaje(TipoMensaje.DATOS_SUSCRIPTOS);
			
			ContenidoDatoSuscripto cont = new ContenidoDatoSuscripto();
			
			cont.setIdentificador(identificador);
			cont.setValor(resultado.getValor().name());
			
			msj.setContenido(cont);
			
			Contexto.instance.getProtocoloSC().enviarMensaje(msj);
		}
	}

	@Override
	public boolean isOcupado() {
		return ocupado;
	}
	
	@Override
	public synchronized void doCallback(Identificador identificadorDeDato, Collection<Dato> memoriaLocalAAlgoritmo) throws InterruptedException {

		logger.info(String.format("Falta el dato %s para el modelo", identificadorDeDato));
		long t0 = System.currentTimeMillis();
		long t1 = System.currentTimeMillis();
		
		Dato dato = null;
		while (t1 - t0 < this.waitForMissingData) {
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Buscando el dato %s", identificadorDeDato));
			}
			dato = AdministradorMemoriaCompartida.getInstance().leerDato(identificadorDeDato);
			
			if (dato != null) {
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Dato %s obtenido desde memoria compartida", identificadorDeDato));
				}

				memoriaLocalAAlgoritmo.add(dato);
				break;
			}
			
			dato = DatosFetcher.instance.fetchDato(identificadorDeDato);
			
			if (dato != null) {

				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Dato %s obtenido desde base de datos", identificadorDeDato));
				}

				memoriaLocalAAlgoritmo.add(dato);
				break;
			}
			
			this.wait(SLEEP_TIME);
			t1 = System.currentTimeMillis();
		}
		
	}

	@Override
	public void asignarModelo(ModeloEjecutable modeloEjecutable) {
		try {
			this.colaDeModelos.put(modeloEjecutable);
		} catch (InterruptedException e) {
		}
	}

}
